package com.issac.OnsiteSearchEngine.searcher;

import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class Parser {
    /**
     * Parser：完成制作索引的过程
     * 1. 读取下载好的文档
     * 2. 解析读取到的文档
     * 3. 索引制作
     */

    // 读取下载好的文档
    // 指定加载文档的路径
    private static final String INPUT_FILE_PATH = "D:/ISSAC/Code/JavaProject/jdk-17.0.14_doc-all/docs/api/";

    private Index index = new Index();

    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    // 单线程制作
    private void createIndex() {
        /**
         * Parser类的入口
         * 1. 根据指定路径枚举处路径中的所有html（所有子目录都需要进入遍历）
         * 2. 针对枚举出的html：
         *      1）打开文件
         *      2）读取内容
         *      3）进行解析
         *      4）构建索引
         * 3. 把内存中构造好的索引数据结构 --> 保存在指定文件中
         */

        long beginTime = System.currentTimeMillis();

        // ArrayList保存枚举的html，每个元素是File类型
        ArrayList<File> fileList = new ArrayList<>();

        // 1. 枚举html
        System.out.println("开始制作索引......");
        enumFile(INPUT_FILE_PATH, fileList);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕！消耗时间: " + (endEnumFile - beginTime) + "ms");

        // 2. 解析html
        for (File file : fileList) {
            System.out.println("开始解析html: " + file.getAbsolutePath());
            parseHTML(file);
        }
        long endParse = System.currentTimeMillis();
        System.out.println("遍历文件完毕! 消耗时间: " + (endParse - endEnumFile) + "ms");

        // 3. 把内存中构造好的索引数据结构 --> 保存在指定文件中
        index.saveIndexToDisk();
        long endSave = System.currentTimeMillis();
        System.out.println("索引制作完毕! 消耗时间: " + (endSave - beginTime) + "ms");
    }

    // 多线程制作
    private void createIndexByTread() throws InterruptedException{
        long beginTime = System.currentTimeMillis();

        // ArrayList保存枚举的html，每个元素是File类型
        ArrayList<File> fileList = new ArrayList<>();

        // 1. 枚举html
        System.out.println("开始制作索引......");
        enumFile(INPUT_FILE_PATH, fileList);

        // 2. 解析html，引入"线程池"完成多线程解析
        CountDownLatch latch = new CountDownLatch(fileList.size());
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for (File file : fileList) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析文件：" + file.getAbsolutePath());
                    parseHTML(file);
                    latch.countDown();
                }
            });
        }
        // await方法会阻塞, 直到所有的选手都调用countDown撞线之后, 才能阻塞结束
        latch.await();
        // 手动把线程池里的线程都干掉
        executorService.shutdown();

        // 3. 把内存中构造好的索引数据结构 --> 保存在指定文件中
        index.saveIndexToDisk();

        long endTime = System.currentTimeMillis();
        System.out.println("索引制作完毕! 消耗时间: " + (endTime - beginTime) + "ms");
        System.out.println("t1: " + t1 + ", t2: " + t2);
    }

    // 解析html信息
    private void parseHTML(File file) {
        /**
         * 1. 解析出html的标题
         * 2. 解析出html对应的url
         * 3. 解析出html对应的正文
         * 4. 解析出的信息放入索引
         */

        // 1. 解析出html的标题
        String title = parseTitle(file);

        // 2. 解析出html对应的url
        String url = parseUrl(file);
        long beginTime = System.nanoTime();
        // 3. 解析出html对应的正文
//        String content = parseContent(file);
        String content = parseContentByRegex(file);
        long endParseTime = System.nanoTime();

        // 4. 解析出的信息放入索引
        index.addDocToIndex(title, url, content);
        long endTime = System.nanoTime();

        t1.addAndGet(endParseTime - beginTime);
        t2.addAndGet(endTime - endParseTime);
    }

    // 常规方式对文档正文分词
    public String parseContent(File file) {
        // <div>...正文内容...</div>
        // 以 < > 这些符号作为"是否要进行拷贝"的条件
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024 * 1024)) {
            // 使用try-catch可以保证及时关闭文档

            // 是否要进行拷贝的开关
            boolean isCopy = true;

            // StringBuilder保存结果
            StringBuilder parseResult = new StringBuilder();

            while (true) {
                // 这里使用int类型便于判断文件是否读取结束
                int ret = bufferedReader.read();
                if (ret == -1) {
                    // 表示文件读完
                    break;
                }

                char c = (char) ret;
                if (isCopy) {
                    // 在开关打开的状态下：遇到普通字符就拷贝到StringBuilder中
                    if (c == '<') {
                        // 关闭开关
                        isCopy = false;
                        continue;
                    }
                    if (c == '\n' || c == '\r') {
                        // 把 换行 替换成 空格
                        c = ' ';
                    }
                    // 其他常规字符直接拷贝
                    parseResult.append(c);
                } else {
                    // 开关关闭的状态：不拷贝内容，知道开关再次打开
                    if (c == '>') {
                        isCopy = true;
                    }
                }
            }
            return parseResult.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String readFile(File file) {
        // 以 < > 这些符号作为"是否要进行拷贝"的条件
        // BufferedReader 是一种带缓冲的字符读取器，用于高效读取文本数据(可以逐字符)
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file), 1024 * 1024)) {
            // StringBuilder保存结果
            StringBuilder parseResult = new StringBuilder();

            while (true) {
                // 这里使用int类型便于判断文件是否读取结束
                int ret = bufferedReader.read();
                if (ret == -1) {
                    // 表示文件读完
                    break;
                }
                char c = (char) ret;
                if (c == '\n' || c == '\r') {
                    // 把 换行 替换成 空格
                    c = ' ';
                }
                // 其他常规字符直接拷贝
                parseResult.append(c);
            }
            return parseResult.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    // 正则化方法对文档正文分词
    // 基于正则表达式：实现去标签、去除script
    public String parseContentByRegex(File file) {
        // 1. 整个文档读到String
        String content = readFile(file);
        // 2. 替换掉 script 标签
        content = content.replaceAll("<script.*?>(.*?)</script>", " ");
        // 3. 替换掉普通html标签
        content = content.replaceAll("<.*?>", " ");
        // 4. 使用正则把多个空格合并成一个空格
        content = content.replaceAll("\\s+", " ");
        return content;
    }

    private String parseUrl(File file) {
        /**
         * 构造 Oracle 官网上的 URL
         * final url = 固定前缀 + file解析出的子路径
         */
        String fixedPrefix = "https://docs.oracle.com/en/java/javase/17/docs/api/";
        String part2 = file.getAbsolutePath()
                .replace("\\", "/")
                .substring(INPUT_FILE_PATH.length());
        return fixedPrefix + part2;
    }

    private String parseTitle(File file) {
        String fileName = file.getName();
        // 截取掉.html
        return fileName.substring(0, fileName.length() - ".html".length());
    }

    // 枚举所有html片段
    private void enumFile(String inputFilePath, ArrayList<File> fileList) {
        File rootPath = new File(inputFilePath);
        // 获取当前目录下的所有文件和子目录
        File[] files = rootPath.listFiles();

        for (File file : files) {
            // files 只能获取到rootPath下的一级目录
            // 在枚举过程中进一步判断是否还需要进入子目录枚举
            if (file.isDirectory()) {
                enumFile(file.getAbsolutePath(), fileList);
            } else {
                if (file.getAbsolutePath().endsWith(".html")) {
                    fileList.add(file);
                }
            }
        }
    }


    public static void main(String args[]) throws InterruptedException {
        /**
         * 程序入口，创建 Parser 实例并调用 createIndex() 方法，执行整个流程
         */
        Parser parser = new Parser();
//        parser.createIndex();
        parser.createIndexByTread();
    }


}
